package org.runecore.rs2.model.mask;

import java.util.Deque;
import java.util.LinkedList;

import org.runecore.rs2.model.Entity;
import org.runecore.rs2.model.Location;
import org.runecore.util.Misc;

/**
 * RuneCore
 * Sep 22, 2011
 * @author Harry Andreas<Harry@RuneCore.org>
 */
public class Movement {

	/**
	 * The <code>Entity</code> this handler is for
	 */
	private final Entity entity;
	
	/**
	 * A <code>Deque</code> of walk points
	 */
	private Deque<Point> waypoints = new LinkedList<Point>();
	
	/**
	 * An <code>Integer</code> for run energy
	 */
	private int runEnergy;
	
	private boolean resetWalkingQueue, needsPlacement;
	
	/**
	 * Variables for running
	 */
	private boolean runToggled = false;
	private boolean runPath = false;
	
	public static final byte[] DIRECTION_DELTA_X = new byte[] { 
		-1, 0, 1, -1, 1, -1, 0, 1 
	};
	
	public static final byte[] DIRECTION_DELTA_Y = new byte[] { 
		1, 1, 1, 0, 0, -1, -1, -1 
	};

	/**
	 * Creates a new MovementHandler.
	 * 
	 * @param entity
	 *            the entity
	 */
	public Movement(Entity entity) {
		this.entity = entity;
	}

	public void process() {
		Point walkPoint = null;
		Point runPoint = null;

		// Handle the movement.
		walkPoint = waypoints.poll();
		if (isRunToggled() || isRunPath()) {
			if (getRunEnergy() > 0) {
				runPoint = waypoints.poll();
				if (runPoint != null) {
					setRunEnergy(getRunEnergy() - 1);
					//entity.getActionSender().sendEnergy();
				}
			} else {
				setRunToggled(false);
				//entity.getActionSender().sendConfig(173, 0);
			}
		}
		if (walkPoint != null && walkPoint.getDirection() != -1) {
			entity.getLocation().move(DIRECTION_DELTA_X[walkPoint.getDirection()], DIRECTION_DELTA_Y[walkPoint.getDirection()]);
			entity.setPrimaryDirection(walkPoint.getDirection());
		}
		if (runPoint != null && runPoint.getDirection() != -1) {
			entity.getLocation().move(DIRECTION_DELTA_X[runPoint.getDirection()], DIRECTION_DELTA_Y[runPoint.getDirection()]);
			entity.setSecondaryDirection(runPoint.getDirection());
		}
		// Check for region changes.
		int deltaX = entity.getLocation().getX() - entity.getCurrentRegion().getRegionX() * 8;
		int deltaY = entity.getLocation().getY() - entity.getCurrentRegion().getRegionY() * 8;
		if (deltaX < 16 || deltaX >= 88 || deltaY < 16 || deltaY > 88) {
			//entity.getActionSender().sendMapRegion();
		}
	}
	
	

	/**
	 * Resets the walking queue.
	 */
	public void reset() {
		setRunPath(false);
		waypoints.clear();

		// Set the base point as this position.
		Location p = entity.getLocation();
		waypoints.add(new Point(p.getX(), p.getY(), -1));
	}

	/**
	 * Finishes the current path.
	 */
	public void finish() {
		waypoints.removeFirst();
	}

	/**
	 * Adds a position to the path.
	 * 
	 * @param position
	 *            the position
	 */
	public void addToPath(Location position) {
		if (waypoints.size() == 0) {
			reset();
		}
		Point last = waypoints.peekLast();
		int deltaX = position.getX() - last.getX();
		int deltaY = position.getY() - last.getY();
		int max = Math.max(Math.abs(deltaX), Math.abs(deltaY));
		for (int i = 0; i < max; i++) {
			if (deltaX < 0) {
				deltaX++;
			} else if (deltaX > 0) {
				deltaX--;
			}
			if (deltaY < 0) {
				deltaY++;
			} else if (deltaY > 0) {
				deltaY--;
			}
			addStep(position.getX() - deltaX, position.getY() - deltaY);
		}
	}

	/**
	 * Adds a step.
	 * 
	 * @param x
	 *            the X coordinate
	 * @param y
	 *            the Y coordinate
	 */
	private void addStep(int x, int y) {
		if (waypoints.size() >= 100) {
			return;
		}
		Point last = waypoints.peekLast();
		int deltaX = x - last.getX();
		int deltaY = y - last.getY();
		int direction = Misc.direction(deltaX, deltaY);
		if (direction > -1) {
			waypoints.add(new Point(x, y, direction));
		}
	}

	/**
	 * Toggles the running flag.
	 * 
	 * @param runToggled
	 *            the flag
	 */
	public void setRunToggled(boolean runToggled) {
		this.runToggled = runToggled;
	}

	/**
	 * Gets whether or not run is toggled.
	 * 
	 * @return run toggled
	 */
	public boolean isRunToggled() {
		return runToggled;
	}

	/**
	 * Toggles running for the current path only.
	 * 
	 * @param runPath
	 *            the flag
	 */
	public void setRunPath(boolean runPath) {
		this.runPath = runPath;
	}

	/**
	 * Gets whether or not we're running for the current path.
	 * 
	 * @return running
	 */
	public boolean isRunPath() {
		return runPath;
	}
	
	/**
	 * A <code>Boolean</code> to check if the <code>Entity</code>
	 * is in distance of a location
	 * @param position
	 * @param i
	 * @return
	 */
	public boolean walkToAction(Location position, int i) {
		boolean distance = Misc.getDistance(entity.getLocation(), position) == i;
		return distance && entity.getPrimaryDirection() < 1 && entity.getSecondaryDirection() < 1;
	}
	
	/**
	 * 
	 */
	public void resetOnWalkPacket() {
	}
	
	/**
	 * 
	 * @return
	 */
	public boolean canWalk() {
		return true;
	}

	/**
	 * @return the runEnergy
	 */
	public int getRunEnergy() {
		return runEnergy;
	}

	/**
	 * @param runEnergy the runEnergy to set
	 */
	public void setRunEnergy(int runEnergy) {
		this.runEnergy = runEnergy;
	}

	/**
	 * @return the resetWalkingQueue
	 */
	public boolean isResetWalkingQueue() {
		return resetWalkingQueue;
	}

	/**
	 * @param resetWalkingQueue the resetWalkingQueue to set
	 */
	public void setResetWalkingQueue(boolean resetWalkingQueue) {
		this.resetWalkingQueue = resetWalkingQueue;
	}

	/**
	 * @return the needsPlacement
	 */
	public boolean isNeedsPlacement() {
		return needsPlacement;
	}

	/**
	 * @param needsPlacement the needsPlacement to set
	 */
	public void setNeedsPlacement(boolean needsPlacement) {
		this.needsPlacement = needsPlacement;
	}

	/**
	 * An internal Location type class with support for direction.
	 * 
	 * @author blakeman8192
	 */
	private class Point extends Location {

		private int direction;

		/**
		 * Creates a new Point.
		 * 
		 * @param x
		 *            the X coordinate
		 * @param y
		 *            the Y coordinate
		 * @param direction
		 *            the direction to this point
		 */
		public Point(int x, int y, int direction) {
			super(x, y, 0);
			setDirection(direction);
		}

		/**
		 * Sets the direction.
		 * 
		 * @param direction
		 *            the direction
		 */
		public void setDirection(int direction) {
			this.direction = direction;
		}

		/**
		 * Gets the direction.
		 * 
		 * @return the direction
		 */
		public int getDirection() {
			return direction;
		}

	}

}
